package game;
import game.controlling.ControlHandling;
import game.controlling.KeyListener;
import game.damageSystem.DamageSystem;
import game.engine.Engine;
import game.engine.GSync;
import game.logic.GameLogic;
import game.objects.GOBox;
import game.objects.GODistanceJointTester;
import game.objects.GOMap;
import game.objects.GOPlayer;
import game.objects.GORect;
import game.objects.GameObject;
import game.objects.npc.GOBug;
import game.objects.npc.GODrone;

import graphics.objects.GraphicsObject;
import graphics.system.Color;
import java.io.Serializable;
import java.util.Vector;

import network.protocol.NetworkHandler;
import network.protocol.ServerHandler;

import physics.Body;
import physics.PhysicalConstants;
import physics.joints.JDistanceJoint;
import physics.joints.Joint;
import physics.tools.Initializer;
import tools.Floatmath;
import tools.Help;
import tools.Point;
import tools.Profiler;
import tools.id.IdSystem;
/**
 * This class manages the entire game. It contains the engine, which handles graphics, physics and the network, as well as the game logic that lets players enter vehicles, collect things, make points and win/lose etc. It also contains a list of GameObjects. These are needed to connect the engine to the game system, since some methods in the game system will need to access parts of the engine (dead players fall down, explosions must be visible, etc.)
 * 
 * @author toni
 * 
 */
public class Game implements Serializable {
	public Engine engine;
	public GameLogic logics;
	public ControlHandling controlHandling;
	public transient NetworkHandler network;
	private IdSystem<GameObject> gameObjects;
	public int numGameObjects = 0;
	public float time;
	public boolean server;
	public int frame;
	private InitializeInfo initializeInfo;
	private GOMap map;
	private String mapname;
	public Game(NetworkHandler n, String mapname) {
		engine = new Engine(this);
		logics = new GameLogic(this);
		controlHandling = new ControlHandling(this, logics);
		network = n;
		gameObjects = new IdSystem<GameObject>();
		this.mapname = mapname;
		// When this constructor is called, this game always belong to the
		// server because otherwise the game would have been created by
		// serialization
		n.setGame(this);
		setServer();
	}
	private void setServer() {
		if (network instanceof ServerHandler)
			server = true;
		else
			server = false;
	}
	public void handle() {
		frame++;
		engine.handle();
		Profiler.startNet();
		network.handle();
		Profiler.stopNet();
		logics.handle();
		controlHandling.handle();
		time += PhysicalConstants.DT;
	}
	public void initialize() {
		if (!server)
			return;
//		 addGameObject(new GORect(this, new Point(-410, 1000), 20, 2000, 0.0f, 0.0f, new Color(0, 0, 0, 1), 17, 3));
//		 addGameObject(new GORect(this, new Point(1610, 1000), 20, 2000, 0.0f, 0.0f, new Color(0, 0, 0, 1)));
//		 addGameObject(new GORect(this, new Point(600, -100), 1000, 20, 0.0f, 0.0f, new Color(0, 0, 0, 1)));
//		 addGameObject(new GORect(this, new Point(600, 2000), 1000, 20, 0.0f, 0.0f, new Color(0, 0, 0, 1)));
		this.map = new GOMap(this, mapname);
		addGameObject(map);
		//addGameObject(new GOBox(this,new Point(0,600),20,50,1,0));
		/*GameObject o = new GORect(this, new Point(000, 000), 10, 10, 0.0f, 0.0f, new Color(0, 0, 0, 1));
		addGameObject(o);
		Body lastBody = o.getBody(0);
		//
		for (int i = 0; i < 50; i++) {
			Body b2 = Initializer.getRectangle(new Point(0, 50 * i + 50), 20, 20, 1.0f, 0.0f);
			o.addBody(b2);
			o.addJoint(Initializer.dJoint(lastBody, b2));
			lastBody = b2;
		}
		lastBody.applyImpulse(new Point(5000, 0));*/
	}
	public GOMap getMap() {
		return this.map;
	}
	public void setNetworkHandler(NetworkHandler n) {
		network = n;
		setServer();
	}
	/**
	 * No GameObject is allowed to add bodies, joints or graphicsObjects while still in the constructor, but has to use the initialize method. This is sometimes annoying because its often necessary to give position, rotation or even scale information to the game object upon creation but no one wants to save this information in the gameObject itself.
	 * 
	 * Initialize info keeps track of the information, combined with the id of the gameObject that needs them and calls the particular initialize method.
	 * 
	 * @author toni
	 * 
	 * @param pos
	 * @param angle
	 * @param scale
	 */
	private void initializeObject(GameObject go) {
		if (initializeInfo != null)
			initializeInfo.initialize(go);
		initializeInfo = null;
	}
	public void addInitializeInfo(Point pos, float angle, float scale) {
		initializeInfo = new InitializeInfo(pos, angle, scale);
	}
	public void addInitializeInfo(Point pos, float angle) {
		initializeInfo = new InitializeInfo(pos, angle);
	}
	public void addInitializeInfo(Point pos) {
		initializeInfo = new InitializeInfo(pos);
	}
	public NetworkHandler getNetworkHandler() {
		return network;
	}
	public void addGameObject(GameObject go, boolean fromSystem) {
		if (!fromSystem && !server)
			return;
		numGameObjects++;
		gameObjects.add(go);
		network.addGameObject(go);
		initializeObject(go);
	}
	public void addGameObject(GameObject go) {
		addGameObject(go, false);
	}
	public void remGameObject(GameObject go, boolean fromSystem) {
		if (!fromSystem && !server)
			return;
		numGameObjects--;
		gameObjects.remove(go);
		engine.removeGameObject(go);
		network.remGameObject(go);
	}
	public void remGameObject(GameObject go) {
		remGameObject(go, false);
	}
	public boolean addBody(Body b, GameObject go) {
		if (!((go == null)) && !server)
			return false;
		engine.addBody(b);
		network.addBody(go, b);
		return true;
	}
	public boolean remBody(Body b, GameObject go) {
		if (!((go == null)) && !server)
			return false;
		engine.remBody(b);
		network.remBody(go, b);
		return true;
	}
	public boolean addJoint(Joint j, GameObject go) {
		if (!((go == null)) && !server)
			return false;
		engine.addJoint(j);
		network.addJoint(go, j);
		return true;
	}
	public boolean remJoint(Joint j, GameObject go) {
		if (!((go == null)) && !server)
			return false;
		engine.remJoint(j);
		network.remJoint(go, j);
		return true;
	}
	public boolean addGraphicsObject(GraphicsObject o, GSync s, GameObject go) {
		if (!((go == null)) && !server)
			return false;
		engine.addGraphicsObject(o);
		network.addGraphicsObject(go, s, o);
		return true;
	}
	public boolean remGraphicsObject(GraphicsObject o, GameObject go) {
		if (!((go == null)) && !server)
			return false;
		engine.remGraphicsObject(o);
		network.remGraphicsObject(go, o);
		return true;
	}
	public GameObject getGameObject(int i) {
		return gameObjects.get(i);
	}
	public GameObject getGOById(int id) {
		return gameObjects.getById(id);
	}
	public boolean containsGO(int goId) {
		return gameObjects.containsId(goId);
	}
	public GameObject getContainingGO(Body b) {
		for (int i = 0; i < numGameObjects; i++) {
			GameObject o = getGameObject(i);
			if (o.containsBody(b)) {
				return o;
			}
		}
		return null;
	}
	public GameObject getContainingGO(Joint j) {
		for (int i = 0; i < numGameObjects; i++) {
			GameObject o = getGameObject(i);
			if (o.containsJoint(j)) {
				return o;
			}
		}
		return null;
	}
	public GameObject getContainingGO(GraphicsObject g) {
		for (int i = 0; i < numGameObjects; i++) {
			GameObject o = getGameObject(i);
			if (o.containsGraphicsObject(g)) {
				return o;
			}
		}
		return null;
	}
	public void addPlayer(int id) {
		controlHandling.addPlayer(network.id, id);
		network.addPlayer(id);
	}
	public void remPlayer(int playerId) {
		controlHandling.remPlayer(playerId);
	}
	public void setKeyListener(KeyListener keyListener) {
		controlHandling.setKeyListener(keyListener);
	}
	// only used for offline use (in editors)
	public void setServer(boolean s) {
		this.server = s;
	}
	public void setCampos(Point pos) {
		engine.graphics.setCampos(pos);
	}
	public Vector<GameObject> getAllPlayer() {
		Vector<GameObject> pl = new Vector<GameObject>();
		for (int i = 0; i < this.numGameObjects; i++) {
			if (this.getGameObject(i) instanceof GOPlayer) {
				pl.add(getGameObject(i));
			}
		}
		return pl;
	}
}
